﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

using JoyServer.Events;

namespace JoyServer.Net
{
	/// <summary>
	/// ジョイスティックの情報を送信するためのサーバ
	/// </summary>
	class TcpServer : EventDispatcher
	{
		/// <summary>
		/// 待ち受けポートのデフォルト値
		/// </summary>
		public const int DEFAULT_PORT = 17996;

		/// <summary>
		/// 待ち受けポート
		/// </summary>
		private int port;

		/// <summary>
		/// TCP 待ち受けオブジェクト
		/// </summary>
		private TcpListener tcpListener;

		/// <summary>
		/// 接続してきたクライアントの情報
		/// </summary>
		private List<ClientConnection> connections;

		/// <summary>
		/// 待ち受けスレッド
		/// ブロッキングモードで動作するので、
		/// 別スレッドで起動
		/// </summary>
		private Thread listenerThread;

		/// <summary>
		/// サーバ停止フラグ
		/// </summary>
		private bool isStop;

		// --------------------------------------------------------------------

		/// <summary>
		/// コンストラクタ
		/// </summary>
		public TcpServer(int port)
		{
			this.port = port;
			this.isStop = false;
			this.connections = new List<ClientConnection>();
		}

		/// <summary>
		/// サーバ開始
		/// </summary>
		public void start()
		{
			// 既に開始されている時はそのまま終了
			if (isStarted())
				return;

			isStop = false;

			// サーバ待ち受けスレッドを開始する
			startListenerThread();
		}

		/// <summary>
		/// サーバ停止
		/// </summary>
		public void stop()
		{
			isStop = true;

			// クラアントとの接続を全て閉じる
			closeAllClients();

			// 待ち受けを停止する
			stopListenerThread();

			dispatchNotifyEvent("サーバが停止されました。");
		}


		/// <summary>
		/// 待ち受け用スレッドが開始されているか確認
		/// </summary>
		/// <returns></returns>
		private bool isStarted()
		{
			if (listenerThread != null)
			{
				return true;
			}
			return false;
		}

		/// <summary>
		/// サーバの待ち受け用スレッドを開始する
		/// </summary>
		private void startListenerThread()
		{
			ThreadStart threadStart = new ThreadStart(listen);
			listenerThread = new Thread(threadStart);
			listenerThread.Start();
		}

		/// <summary>
		/// サーバの待ち受け用スレッドを停止する
		/// </summary>
		private void stopListenerThread()
		{
			// 待ち受けを停止する
			if (tcpListener != null)
				tcpListener.Stop();

			// 待ち受けスレッドを停止する
			if (listenerThread != null)
			{
				listenerThread.Join();
				listenerThread = null;
			}
		}

		/// <summary>
		/// 接続中の全てのクライアントを切断する
		/// </summary>
		private void closeAllClients()
		{
			foreach (ClientConnection connection in connections)
			{
				connection.close();
			}
			connections.Clear();
		}

		/// <summary>
		/// メッセージ通知用イベントの発行
		/// </summary>
		/// <param name="message"></param>
		private void dispatchNotifyEvent(String message)
		{
			ServerEvent e;
			e = new ServerEvent(this, ServerEvent.NOTIFY);
			e.message = "JoyServer: " + message;
			dispatchEvent(e);
		}

		/// <summary>
		/// データ受信イベントの発行
		/// </summary>
		/// <param name="connection"></param>
		private void dispatchReceiveEvent(ClientConnection connection)
		{
			//dispatchNotifyEvent("dispatchReceiveEvent() : 受信イベント発行 : " + _packet.toString());

			// 受信イベント発行
			ServerEvent serverEvent;
			serverEvent = new ServerEvent(this, ServerEvent.PACKET_RECEIVED);
			serverEvent.packet = connection.packet;
			connection.packet = null;
			dispatchEvent(serverEvent);
		}

		/// <summary>
		/// サーバ待ち受けループ
		/// 別スレッドで動作する
		/// </summary>
		private void listen()
		{
			dispatchNotifyEvent("サーバが開始されました。");

			try
			{
				tcpListener = new TcpListener(IPAddress.Loopback, port);
				tcpListener.Start();

				dispatchNotifyEvent("接続を待っています。");
				while (!isStop)
				{
					checkPendingAndAccept();
					Thread.Sleep(10);
				}
			}
			catch (SocketException e)
			{
				dispatchNotifyEvent("エラー (SocketException) : " + e.ToString());
			}
			finally
			{
				tcpListener.Stop();
			}
		}

		/// <summary>
		/// クライアントの接続待ちと接続受け入れ
		/// </summary>
		private void checkPendingAndAccept()
		{
			if (tcpListener.Pending())
			{
				TcpClient client = tcpListener.AcceptTcpClient();
				ClientConnection connection = new ClientConnection();
				connection.addEventListener(ClientEvent.DATA_RECEIVE, onClientDataReceive);
				connection.addEventListener(ClientEvent.CLOSE, onClientClose);
				connection.start(client);
				connections.Add(connection);

				if (client.Client.Connected == true)
					dispatchNotifyEvent("クライアントが接続しました。 : " + 
										client.Client.RemoteEndPoint.ToString());
				
				dispatchNotifyEvent("現在の接続数: " + connections.Count);
			}
		}

		/// <summary>
		/// ClientConnection オブジェクトから送信される
		/// データ受信イベント
		/// </summary>
		/// <param name="e"></param>
		private void onClientDataReceive(Event e)
		{
			ClientEvent clientEvent = e as ClientEvent;
			if (clientEvent == null)
				return;

			ClientConnection connection = clientEvent.connection;

			lock (connection)
			{
				ByteArray data = clientEvent.data;
				data.position = 0;

				uint bytesAvailable = data.length - data.position;
				//Debug.WriteLine("onDataReceive: " + bytesAvailable);

				while (bytesAvailable > 0)
				{
					// ソケットの残りデータが必要バイト数に満たない場合はループ終了
					if (connection.packetNecessaryBytesLength > bytesAvailable)
					{
						break;
					}

					switch (connection.packetPhase)
					{
						// パケットタイプ (Short)
						case 0:
							if (bytesAvailable >= 2)
							{
								connection.packet = new Packet();
								connection.packet.type = data.readUnsignedShort();
								bytesAvailable = data.length - data.position;
								//Debug.WriteLine("Phase 1: " + bytesAvailable + ", " + connection.packet.type);
								connection.packetNecessaryBytesLength = 0;
								connection.packetPhase++;

							}
							else
							{
								connection.packetNecessaryBytesLength = 2;
							}
							break;

						// パケット長 (Short)
						case 1:
							if (bytesAvailable >= 2)
							{
								connection.packet.remain = data.readUnsignedShort();
								bytesAvailable = data.length - data.position;
								//Debug.WriteLine("Phase 2: " + bytesAvailable);
								connection.packetNecessaryBytesLength = 0;
								connection.packetPhase++;

								// データ部が無いパケットの場合
								if (connection.packet.remain == 0)
								{
									connection.packetPhase = 0;
									onReceivePakcet(connection);
									break;
								}
							}
							else
							{
								connection.packetNecessaryBytesLength = 2;
							}
							break;

						// データ (ByteArray)
						case 2:
							// データの一部のみ受信できた場合
							if (bytesAvailable < connection.packet.remain)
							{
								data.readBytes(connection.packet.data,
												  connection.packet.data.position,
												  bytesAvailable);
								bytesAvailable = data.length - data.position;
								//Debug.WriteLine("Phase 3-1: " + bytesAvailable);
								connection.packet.remain -= bytesAvailable;
							}
							// 残り全てが受信できた場合
							else
							{
								data.readBytes(connection.packet.data,
												  connection.packet.data.position,
												  connection.packet.remain);
								bytesAvailable = data.length - data.position;
								//Debug.WriteLine("Phase 3-2: " + bytesAvailable);
								connection.packetNecessaryBytesLength = 0;
								connection.packetPhase = 0;
								onReceivePakcet(connection);
							}
							break;
					}
				}
			}
		}

		/// <summary>
		/// ClientConnection オブジェクトから送信される
		/// クライアント切断完了イベント
		/// </summary>
		/// <param name="e"></param>
		private void onClientClose(Event e)
		{
			ClientEvent clientEvent = e as ClientEvent;
			if (clientEvent == null)
				return;

			ClientConnection connection = clientEvent.connection;
			connections.Remove(connection);
			dispatchNotifyEvent("クライアントの接続を閉じました。 : " +
								connection.endPointString);
			dispatchNotifyEvent("現在の接続数: " + connections.Count);
		}

		/// <summary>
		/// パケット受信
		/// onClientDataReceive メソッドからのみ呼び出される。
		/// </summary>
		/// <param name="connection"></param>
		private void onReceivePakcet(ClientConnection connection)
		{
			// パケットの後処理
			connection.packet.remain = 0;

			// パケット受信イベントを発行
			//dispatchReceiveEvent(connection);

			//Debug.WriteLine("connection.packet.type: " + connection.packet.type);

			// ジョイスティックデータを取得してクライアントに送信
			if (connection.packet.type < 100)
			{
				uint joystickNumber = connection.packet.type;

				Packet packet = PacketBuilder.createJoystickDataPacket(joystickNumber);
				connection.sendPacket(packet);
			}
			else
			{
				switch (connection.packet.type)
				{
					case SystemPacketType.DEVICE_LIST:
						Packet packet = PacketBuilder.createDeviceListPacket();
						connection.sendPacket(packet);
						break;
				}
			}
		}

	}
}
